// /////////////////////////////////////////////////////////////////////////////
//
// Name:            SoundSourceComponent.cpp
// Author:          Michael Bartsch (ZCCdark203)
//
// Desc :           Component wrapper of SFML's sf::Sound and sf::Music.
//
// License:         Copyright (C) 2009 Michael Bartsch and Contributors
//
//                  This program is free software: you can redistribute it
//                  and/or modify it under the terms of the zlib/libpng License.
//                  See main.cpp for conditions of distribution and use.
//
// /////////////////////////////////////////////////////////////////////////////

// Include files
#include "SoundSourceComponent.h"
#ifdef __COMPILE_WITH_SFML_AUDIO__
#include "../../core/GameManager.h"


// SoundSourceComponent class
// SoundSource constructor.
SoundSourceComponent::SoundSourceComponent(Entity *parent)
: EntityComponent(parent), mSound(NULL), mMusic(NULL), mBuffer(NULL), mAttenuation(1.0f),
  mLoop(false), mMinDistance(1.0f), mPitch(1.0f), mPosition(parent->getAbsolutePosition()), mVolume(100)
{
    if(pParent != NULL)
    {
       // Set misc. variables.
       mName = "SoundSourceComponent";

       // Register events.
       pParent->connectEventSignal("onPositionChange", this, &SoundSourceComponent::onPositionChange);
       pParent->connectEventSignal("onPause", this, &SoundSourceComponent::onPause);
       pParent->connectEventSignal("onUnPause", this, &SoundSourceComponent::onUnPause);
    }
}

// SoundSourceComponent deconstructor.
SoundSourceComponent::~SoundSourceComponent()
{
    if(pParent != NULL)
    {
       if(mSound != NULL)
         delete mSound;

       if(mMusic != NULL)
         delete mMusic;

       if(mBuffer != NULL)
         delete[] mBuffer;
    }
}

// Returns a direct pointer to the sf::Sound object in this component.
sf::Sound* SoundSourceComponent::getSound()
{
    return mSound;
}

// Returns a direct pointer to the sf::Music object in this component.
sf::Music* SoundSourceComponent::getMusic()
{
    return mMusic;
}

// Loads the sound data as a music (streaming) object.
bool SoundSourceComponent::loadMusic(const std::string &fileName)
{
    // Get pointer to the File System of Irrlicht.
    IFileSystem *fileSystem = GameManager::Instance()->getDevice()->getFileSystem();

    // Delete the previous data.
    unloadData();

    // Load the sound.
    AssetGroup *assets = pParent->getAssetGroup();

    if(assets != NULL)
    {
       // Try to retrieve sound from the parent's AssetGroup.
       SoundProcessor *processor = static_cast<SoundProcessor*>
                                   (assets->getAssetProcessor("sounds"));

       if(processor->getSound(fileName, mBuffer, mBufferSize))
       {
           assets->connectEventSignal(std::string("sounds/") + fileName, this,
                                     &SoundSourceComponent::onSound);
           mSoundFileName = fileName;
       }

       else
         return false;
    }

    else
    {
       // Does the file exist?
       if(!fileSystem->existFile(fileName.c_str()))
         return false;

       // Open the given file and load the contents to the buffer.
       IReadFile *file = fileSystem->createAndOpenFile(fileName.c_str());

       mBufferSize = file->getSize();
       mBuffer = new c8[mBufferSize+1];
       file->read(mBuffer, mBufferSize);
       file->drop();
    }

    // Create SFML's music object.
    mMusic = new sf::Music();
    mMusic->OpenFromMemory(mBuffer, mBufferSize);

    // Initialize variables.
    mMusic->SetAttenuation(mAttenuation);
    mMusic->SetLoop(mLoop);
    mMusic->SetMinDistance(mMinDistance);
    mMusic->SetPitch(mPitch);
    mMusic->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);
    mMusic->SetVolume(mVolume);

    return true;
}

// Loads the sound data as a music (streaming) object from memory.
bool SoundSourceComponent::loadMusicFromMemory(c8 *buffer, long length)
{
    // Delete the previous data.
    unloadData();

    // Create SFML's music object.
    mMusic = new sf::Music();
    mMusic->OpenFromMemory(buffer, length);

    // Initialize variables.
    mMusic->SetAttenuation(mAttenuation);
    mMusic->SetLoop(mLoop);
    mMusic->SetMinDistance(mMinDistance);
    mMusic->SetPitch(mPitch);
    mMusic->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);
    mMusic->SetVolume(mVolume);

    return true;
}

// Loads the sound data from a sound buffer.
bool SoundSourceComponent::loadSoundBuffer(const std::string &bufferName)
{
    // Delete the previous data.
    unloadData();

    // Set new data.
    mSound = new sf::Sound();

    sf::SoundBuffer *soundBuffer = GameManager::Instance()->getSoundManager()->getSoundBuffer(bufferName);
    if(soundBuffer == NULL)
    {
       delete mSound;
       mSound = NULL;
       return false;
    }

    mSound->SetBuffer(*soundBuffer);
    std::cout << mSound->GetBuffer()->GetDuration() << "\n";

    mSound->SetAttenuation(mAttenuation);
    mSound->SetLoop(mLoop);
    mSound->SetMinDistance(mMinDistance);
    mSound->SetPitch(mPitch);
    mSound->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);
    mSound->SetVolume(mVolume);

    return true;
}

// Unloads previously loaded data.
void SoundSourceComponent::unloadData()
{
    // Get pointer to the parent's asset group.
    AssetGroup *assets = pParent->getAssetGroup();

    // Disconnect from asset group event if there is one.
    /*if(assets != NULL)
      assets->disconnectEventSignal(std::string("sounds/") + mSoundFileName, this);*/

    // Remove sound object.
    if(mSound != NULL)
    {
       delete mSound;
       mSound = NULL;
    }

    if(mMusic != NULL)
    {
       delete mMusic;
       mMusic = NULL;
    }

    if(assets == NULL && mBuffer != NULL)
    {
        delete[] mBuffer;
        mBuffer = NULL;
        mBufferSize = 0;
    }
}

// Plays the associated sound.
void SoundSourceComponent::play()
{
    if(mSound != NULL)
      mSound->Play();

    else if(mMusic != NULL)
      mMusic->Play();
}

// Pauses the associated sound.
void SoundSourceComponent::pause()
{
    if(mSound != NULL)
      mSound->Pause();

    else if(mMusic != NULL)
      mMusic->Pause();
}

// Stops the associated sound.
void SoundSourceComponent::stop()
{
    if(mSound != NULL)
      mSound->Stop();

    else if(mMusic != NULL)
      mMusic->Stop();
}

// Gets the attenuation of the sound.
f32 SoundSourceComponent::getAttenuation() const
{
    return mAttenuation;
}

// Returns whether the sound is paused.
bool SoundSourceComponent::getIsPaused() const
{
    if(mSound != NULL)
    {
       if(mSound->GetStatus() == sf::Sound::Paused)
         return true;
    }

    else if(mMusic != NULL)
    {
        if(mMusic->GetStatus() == sf::Sound::Paused)
          return true;
    }

    return false;
}

// Returns whether the sound is playing.
bool SoundSourceComponent::getIsPlaying() const
{
    if(mSound != NULL)
    {
       if(mSound->GetStatus() == sf::Sound::Playing)
         return true;
    }

    else if(mMusic != NULL)
    {
        if(mMusic->GetStatus() == sf::Sound::Playing)
          return true;
    }

    return false;
}

// Returns whether the sound has been stopped.
bool SoundSourceComponent::getIsStopped() const
{
    if(mSound != NULL)
    {
        if(mSound->GetStatus() == sf::Sound::Stopped)
          return true;
    }

    else if(mMusic != NULL)
    {
        if(mMusic->GetStatus() == sf::Sound::Stopped)
          return true;
    }

    return false;
}

// Gets the loop state of the sound.
bool SoundSourceComponent::getLoop() const
{
    return mLoop;
}

// Gets the minimum distance.
f32 SoundSourceComponent::getMinDistance() const
{
    return mMinDistance;
}

// Gets the current playing position of the sound.
// NOTE: Only works for sound!
f32 SoundSourceComponent::getOffset() const
{
    if(mSound != NULL)
      return mSound->GetPlayingOffset();

    return 0.0f;
}

// Gets the pitch of the sound.
f32 SoundSourceComponent::getPitch() const
{
    return mPitch;
}

// Gets the volume of the sound.
f32 SoundSourceComponent::getVolume() const
{
    return mVolume;
}

// Sets the attenuation factor - the higher the attenuation, the more the sound
// will be attenuated with distance from listener.
void SoundSourceComponent::setAttenuation(f32 attenuation)
{
    if(mSound != NULL)
      mSound->SetAttenuation(attenuation);

    else if(mMusic != NULL)
      mMusic->SetAttenuation(attenuation);

    mAttenuation = attenuation;
}

// Sets the sound loop state.
void SoundSourceComponent::setLoop(bool value)
{
    if(mSound != NULL)
      mSound->SetLoop(value);

    else if(mMusic != NULL)
      mMusic->SetLoop(value);

    mLoop = value;
}

// Sets the minimum distance - closer than this distance, the listener will hear
// the sound at its maximum volume.
void SoundSourceComponent::setMinDistance(f32 minDistance)
{
    if(mSound != NULL)
      mSound->SetMinDistance(minDistance);

    else if(mMusic != NULL)
      mMusic->SetMinDistance(minDistance);

    mMinDistance = minDistance;
}

// Sets the current playing position of the sound.
// NOTE: Only works for sound!
void SoundSourceComponent::setOffset(f32 offset)
{
    if(mSound != NULL)
      mSound->SetPlayingOffset(offset);
}

// Sets the sound pitch.
void SoundSourceComponent::setPitch(f32 pitch)
{
    if(mSound != NULL)
      mSound->SetPitch(pitch);

    else if(mMusic != NULL)
      mMusic->SetPitch(pitch);

    mPitch = pitch;
}

// Sets the sound volume.
void SoundSourceComponent::setVolume(f32 volume)
{
    if(mSound != NULL)
      mSound->SetVolume(volume);

    else if(mMusic != NULL)
      mMusic->SetVolume(volume);

    mVolume = volume;
}

// Events
// Updates the position of the component after its parent has been updated.
void SoundSourceComponent::onPositionChange(void *p)
{
    mPosition += reinterpret_cast<vector3df*>(p)[0];

    if(mSound != NULL)
      mSound->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);

    else if(mMusic != NULL)
      mMusic->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);
}

// Pauses the component if the parent is paused.
void SoundSourceComponent::onPause(void *p)
{
    mWasPaused = getIsPaused();
    pause();
}

// Unpauses the component if the parent is unpaused.
void SoundSourceComponent::onUnPause(void *p)
{
    if(!mWasPaused)
      play();
}

// Responds to changes of the sound data attached to this component.
void SoundSourceComponent::onSound(void *p)
{
    // Get sound data.
    std::pair<c8*, long> *soundData = reinterpret_cast<std::pair<c8*, long>*>(p);

    // Unload previous data.
    unloadData();

    // Set-up new buffer.
    mBuffer = soundData->first;
    mBufferSize = soundData->second;

    // Set sound.
    if(mBufferSize != 0)
    {
       // Create SFML's music object.
       mMusic = new sf::Music();
       mMusic->OpenFromMemory(mBuffer, mBufferSize);

       // Initialize variables.
       mMusic->SetAttenuation(mAttenuation);
       mMusic->SetLoop(mLoop);
       mMusic->SetMinDistance(mMinDistance);
       mMusic->SetPitch(mPitch);
       mMusic->SetPosition(mPosition.X, mPosition.Y, mPosition.Z);
       mMusic->SetVolume(mVolume);
    }
}

// Serializes this object.
bool SoundSourceComponent::onSerialization(SerializedAttributes *attributes)
{
    // Create a root.
    SerializedAttributes *root = new SerializedAttributes();

    // Serialize attributes.
    if(mMusic != NULL)
      root->pushString("music", mSoundFileName);

    root->pushFloat("attenuation", getAttenuation());
    root->pushBool("loop", getLoop());
    root->pushFloat("minDistance", getMinDistance());
    root->pushFloat("pitch", getPitch());
    root->pushFloat("volume", getVolume());

    // Add root to the given collection of attributes.
    attributes->pushObject("SoundSourceComponent", root);
    root->drop();

    // Return
    return true;
}

// Deserializes this object.
bool SoundSourceComponent::onDeserialization(SerializedAttributes *attributes)
{
    // Retrieve root attribute from the collection.
    SerializedAttribute *rootAttribute = attributes->pullAttribute();

    if(rootAttribute == NULL || rootAttribute->getName() != "SoundSourceComponent")
      return false;

    SerializedAttributes *root = reinterpret_cast<SerializedAttributes*>(rootAttribute->getObject());
    root->grab();

    rootAttribute->drop(); // We no longer need this attribute.

    // Process all attributes in the root collection.
    for(SerializedAttribute *attribute = root->pullAttribute(); attribute != NULL; attribute = root->pullAttribute())
    {
       // Deserialize attributes
            if(attribute->getName() == "music")         loadMusic(attribute->getString());
       else if(attribute->getName() == "attenuation")   setAttenuation(attribute->getFloat());
       else if(attribute->getName() == "loop")          setLoop(attribute->getBool());
       else if(attribute->getName() == "minDistance")   setMinDistance(attribute->getFloat());
       else if(attribute->getName() == "pitch")         setPitch(attribute->getFloat());
       else if(attribute->getName() == "volume")        setVolume(attribute->getFloat());

       // We no longer need a reference to the attribute.
       attribute->drop();
    }

    // We no longer need a reference to the root.
    root->drop();

    // We're done.
    return true;
}

#endif // __COMPILE_WITH_SFML_AUDIO__

// End of File
